package nl.saxion.dbi.api;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nl.saxion.dbi.exception.DatabaseAlreadyExistsException;
import nl.saxion.dbi.exception.DatabaseDoesNotExistsException;
import nl.saxion.dbi.exception.FieldAllreadyExistsException;
import nl.saxion.dbi.exception.FieldNotFoundException;
import nl.saxion.dbi.exception.IntegrityConstraintViolationException;
import nl.saxion.dbi.exception.InvalidTypeException;
import nl.saxion.dbi.exception.KeyAllreadyExistsException;
import nl.saxion.dbi.exception.KeyNotFoundException;
import nl.saxion.dbi.exception.KeyPairNotAllowedException;
import nl.saxion.dbi.exception.TableAlreadyExistsException;
import nl.saxion.dbi.exception.TableNotFoundException;
import nl.saxion.dbi.model.Database;
import nl.saxion.dbi.model.Key;
import nl.saxion.dbi.model.KeyPair;
import nl.saxion.dbi.model.Row;
import nl.saxion.dbi.model.Tabel;
import nl.saxion.dbi.model.Type;
import nl.saxion.dbi.model.Veld;

public class RdbmsImpl implements Rdbms
{

	BazenbaseUtility util = BazenbaseUtility.getInstance();

	@Override
	public void clean()
	{
		util.clean();
	}

	@Override
	public void createDatabase(String naam) throws DatabaseAlreadyExistsException
	{

		List<Key> uniqueKeys;
		try
		{
			uniqueKeys = util.getUniqueKeys("bazenbase", "databases");
			Map<String, Object> propertyMap = new HashMap<String, Object>();
			propertyMap.put("naam", naam);

			boolean unique = true;
			for (Database database : util.getDatabases())
			{
				unique = util.isUnique(uniqueKeys, database, propertyMap) ? unique : false;
			}

			if (unique)
			{
				util.addDatabase(naam);
			}
			else
			{
				throw new DatabaseAlreadyExistsException(naam);
			}
		}
		catch (DatabaseDoesNotExistsException e)
		{
			e.printStackTrace();
		}

	}

	@Override
	public void deleteDatabase(String naam) throws IntegrityConstraintViolationException
	{
		Database database = util.getDatabaseByName(naam);
		util.deleteDatabase(database);
		System.out.println("Database " + naam + " verwijderd");
	}

	@Override
	public List<Database> showDatabases()
	{
		return util.getDatabases();
	}

	@Override
	public void createTabel(String databaseNaam, String naam) throws TableAlreadyExistsException,
		DatabaseDoesNotExistsException
	{
		List<Key> uniqueKeys = util.getUniqueKeys("bazenbase", "tabellen");

		Database database = util.getDatabaseByName(databaseNaam);
		if (database != null)
		{
			Map<String, Object> propertyMap = new HashMap<String, Object>();
			propertyMap.put("naam", naam);
			propertyMap.put("database_id", database.getId());

			boolean unique = true;
			for (Tabel tabel : util.getTabellen(databaseNaam))
			{
				unique = util.isUnique(uniqueKeys, tabel, propertyMap) ? unique : false;
			}

			if (unique)
			{
				util.addTable(databaseNaam, naam);
			}
			else
			{
				throw new TableAlreadyExistsException(naam);
			}
		}
		else
		{
			throw new IllegalStateException("Database bestaat niet");
		}
	}

	@Override
	public void deleteTabel(String databaseNaam, String naam) throws IntegrityConstraintViolationException,
		DatabaseDoesNotExistsException
	{
		Tabel tabel = util.getTabelByName(databaseNaam, naam);
		List<Veld> velden = new ArrayList<>(tabel.getVelden());
		for (Veld veld : velden)
		{
			deleteVeld(databaseNaam, naam, veld.getNaam());
		}
		Database database = util.getDatabaseByName(databaseNaam);
		util.deleteTabel(database, tabel);

		System.out.println("Tabel " + naam + " verwijderd");
	}

	@Override
	public Database showDatabase(String database)
	{
		Database result = util.getDatabaseByName(database);
		if (result != null)
		{
			return result;
		}
		throw new IllegalStateException("Database bestaat niet");
	}

	@Override
	public void addVeld(String databaseNaam, String tabelNaam, String naam, String type, boolean notNull)
		throws FieldAllreadyExistsException, InvalidTypeException, DatabaseDoesNotExistsException
	{

		if (!Type.isValidType(type))
		{
			throw new InvalidTypeException(type);
		}

		List<Key> uniqueKeys = util.getUniqueKeys("bazenbase", "velden");
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		if (tabel != null)
		{
			Map<String, Object> propertyMap = new HashMap<String, Object>();
			propertyMap.put("tabel_id", tabel.getId());
			propertyMap.put("naam", naam);
			propertyMap.put("type", type);
			propertyMap.put("not_null", notNull);

			boolean unique = true;
			for (Veld veld : tabel.getVelden())
			{
				unique = util.isUnique(uniqueKeys, veld, propertyMap) ? unique : false;
			}

			if (unique)
			{
				util.addVeld(tabel, naam, type, notNull);
			}
			else
			{
				throw new FieldAllreadyExistsException(naam);
			}

		}
		else
		{
			throw new IllegalStateException("Tabel bestaat niet");
		}
	}

	@Override
	public void deleteVeld(String databaseNaam, String tabelNaam, String veldNaam)
		throws IntegrityConstraintViolationException, DatabaseDoesNotExistsException
	{
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		Database database = util.getDatabaseByName(databaseNaam);

		List<KeyPair> toKeys = util.getForeignToKeysForVeld(databaseNaam, tabelNaam, veldNaam);
		if (toKeys.isEmpty())
		{
			List<Key> keys = new ArrayList<>(tabel.getKeys());
			for (Key key : keys)
			{
				deleteKey(databaseNaam, tabelNaam, key.getNaam());
			}
			util.deleteVeld(database, tabel, veldNaam);
			System.out.println("Veld " + veldNaam + " verwijderd");
		}
		else
		{
			String message = "(";
			String separator = "";
			for (KeyPair keyPair : toKeys)
			{
				message += separator + "(" + keyPair.getFromKey().getNaam() + ", " + keyPair.getToKey().getNaam() + ")";
				separator = ", ";
			}
			message += ")";
			throw new IntegrityConstraintViolationException(message);
		}
	}

	@Override
	public void createKey(String databaseNaam, String tabelNaam, String naam, boolean isUnique, String... veldnamen)
		throws KeyAllreadyExistsException, FieldNotFoundException, DatabaseDoesNotExistsException
	{
		List<Key> uniqueKeys = util.getUniqueKeys("bazenbase", "keys");
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		if (tabel != null)
		{
			Map<String, Object> propertyMap = new HashMap<String, Object>();
			propertyMap.put("naam", naam);
			propertyMap.put("is_unique", isUnique);

			boolean unique = true;
			for (Key key : util.getAllKeys())
			{
				unique = util.isUnique(uniqueKeys, key, propertyMap) ? unique : false;
			}
			if (unique)
			{
				util.addKey(tabel, naam, isUnique, veldnamen);
			}
			else
			{
				throw new KeyAllreadyExistsException(naam);
			}
		}
		else
		{
			throw new IllegalStateException("Tabel bestaat niet");
		}

	}

	@Override
	public void deleteKey(String databaseNaam, String tabelNaam, String keyNaam)
		throws IntegrityConstraintViolationException, DatabaseDoesNotExistsException
	{
		Database database = util.getDatabaseByName(databaseNaam);
		if (database != null)
		{
			Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
			if (tabel != null)
			{
				List<Key> keys = new ArrayList<>(tabel.getKeys());
				for (Key key : keys)
				{
					if (key.getNaam().equals(keyNaam))
					{
						if (util.isToKey(databaseNaam, key))
						{
							throw new IntegrityConstraintViolationException(key.getNaam());
						}
						if (tabel.getPrimaryKey() != null && tabel.getPrimaryKey().getId() == key.getId())
						{
							throw new IntegrityConstraintViolationException(key.getNaam());
						}
						util.deleteKey(key, databaseNaam);
					}
				}
			}
			else
			{
				throw new IllegalStateException("Tabel bestaat niet");
			}
		}
		else
		{
			throw new IllegalStateException("Database bestaat niet");
		}
		System.out.println("Key " + keyNaam + " verwijderd");
	}

	@Override
	public void addPrimaryKey(String databaseNaam, String tabelNaam, String keyNaam) throws KeyNotFoundException,
		TableNotFoundException, DatabaseDoesNotExistsException
	{
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		if (tabel != null)
		{
			util.setPrimaryKey(tabel, keyNaam);
		}
		else
		{
			throw new TableNotFoundException(tabelNaam);
		}
	}

	@Override
	public void addForeignKey(String databaseNaam, String fromKeyNaam, String toKeyNaam) throws KeyNotFoundException,
		KeyPairNotAllowedException, DatabaseDoesNotExistsException
	{
		List<Key> uniqueKeys = util.getUniqueKeys("bazenbase", "keys_keys");
		Database database = util.getDatabaseByName(databaseNaam);
		if (database != null)
		{
			Map<String, Object> propertyMap = new HashMap<String, Object>();
			Key fromKey = util.getKeyByName(databaseNaam, fromKeyNaam);
			Key toKey = util.getKeyByName(databaseNaam, toKeyNaam);
			if (fromKey != null)
			{
				propertyMap.put("from_key", fromKey);
			}
			else
			{
				throw new KeyNotFoundException(fromKeyNaam);
			}

			if (toKey != null)
			{
				propertyMap.put("to_key", toKey);
			}
			else
			{
				throw new KeyNotFoundException(toKeyNaam);
			}

			boolean unique = true;
			for (KeyPair keypair : database.getKeyPairs())
			{
				unique = util.isUnique(uniqueKeys, keypair, propertyMap) ? unique : false;
			}

			if (unique)
			{
				util.addForeignKey(database, fromKey, toKey);
			}
			else
			{
				throw new KeyPairNotAllowedException("(" + fromKeyNaam + ", " + toKeyNaam + ")");
			}
		}
		else
		{
			throw new IllegalStateException("Database " + databaseNaam + " bestaat niet");
		}
	}

	// ------------------- DML ------------------------

	@Override
	public void insertInto(String databaseNaam, String tabelNaam, String[] data)
		throws IntegrityConstraintViolationException, DatabaseDoesNotExistsException
	{
		List<Key> uniqueKeys = util.getUniqueKeys(databaseNaam, tabelNaam);
		Map<String, Object> propertyMap = new HashMap<String, Object>();
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		Database database = util.getDatabaseByName(databaseNaam);

		Row newRow = new Row();
		newRow.setTabel(tabel);
		newRow.setData(data);

		// argument checking
		if (data.length == tabel.getVelden().size())
		{

			// Not null && type checking
			for (Veld veld : tabel.getVelden())
			{
				if (veld.isNotNull() && "null".equals(newRow.getProperty(veld.getNaam())))
				{
					throw new IntegrityConstraintViolationException("Veld " + veld.getNaam() + " mag niet null zijn");
				}

				if (!Type.valueOf(veld.getType()).isValid((String) newRow.getProperty(veld.getNaam())))
				{
					throw new IntegrityConstraintViolationException(newRow.getProperty(veld.getNaam())
						+ " is niet van type " + Type.valueOf(veld.getType()).toString());
				}

			}

			// Unique constraints

			for (int i = 0; i < data.length; i++)
			{
				propertyMap.put(tabel.getVelden().get(i).getNaam(), data[i]);
			}

			boolean unique = true;
			for (Row row : tabel.getData())
			{
				unique = util.isUnique(uniqueKeys, row, propertyMap) ? unique : false;
			}

			if (unique)
			{

				// Foreign Key constraints
				List<KeyPair> keyPairs = util.getFromKeys(database, tabel);
				if (keyPairs.isEmpty())
				{
					util.addRow(tabel, newRow);
					return;
				}

				boolean foreignKeysValid = true;
				for (KeyPair keyPair : keyPairs)
				{
					Map<String, String> foreignKeyPropertyMap = util.buildForeignFromKeyPropertyMap(database, tabel,
						keyPair, data);
					foreignKeysValid = util.isForeignKeyConstraintValid(foreignKeyPropertyMap, keyPair.getToKey()) ? foreignKeysValid
						: false;
				}
				if (foreignKeysValid)
				{
					util.addRow(tabel, newRow);
					return;
				}
				throw new IntegrityConstraintViolationException("Foreign Keys voldoen niet");
			}
			else
			{
				throw new IntegrityConstraintViolationException("Data niet uniek");
			}
		}
		else
		{
			throw new IntegrityConstraintViolationException("Niet genoeg argumenten");
		}

	}

	@Override
	public Tabel selectFrom(String database, String tabel) throws DatabaseDoesNotExistsException
	{
		return util.getTabelByName(database, tabel);
	}

	@Override
	public List<Row> selectWhere(String databaseNaam, String tabelNaam, String veldNaam, String value)
		throws DatabaseDoesNotExistsException
	{
		List<Row> result = new ArrayList<>();
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		Veld veld = util.getVeldByNaam(tabel, veldNaam);
		if (veld != null)
		{
			for (Row row : tabel.getData())
			{
				if (row.getProperty(veld.getNaam()).equals(value))
				{
					result.add(row);
				}
			}
		}
		return result;
	}

	@Override
	public void deleteData(String databaseNaam, String tabelNaam) throws IntegrityConstraintViolationException,
		DatabaseDoesNotExistsException
	{
		Database database = util.getDatabaseByName(databaseNaam);
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		List<KeyPair> keyPairs = util.getToKeys(database, tabel);
		if (keyPairs.isEmpty())
		{
			tabel.setData(new ArrayList<Row>());
			System.out.println("Gegevens uit tabel " + tabelNaam + " verwijderd");
			return;
		}
		else
		{
			// Foreign Key constraints

			boolean canDelete = canDeleteData(tabel.getData(), database, tabel, keyPairs);
			if (canDelete)
			{
				tabel.setData(new ArrayList<Row>());
				System.out.println("Gegevens uit tabel " + tabelNaam + " verwijderd");
				return;
			}
			throw new IntegrityConstraintViolationException("Foreign Keys voldoen niet");
		}
	}

	@Override
	public void deleteDataWhere(String databaseNaam, String tabelNaam, String veldNaam, String value)
		throws DatabaseDoesNotExistsException, IntegrityConstraintViolationException
	{
		Database database = util.getDatabaseByName(databaseNaam);
		Tabel tabel = util.getTabelByName(databaseNaam, tabelNaam);
		List<KeyPair> keyPairs = util.getToKeys(database, tabel);

		// Foreign Key constraints
		List<Row> rows = selectWhere(databaseNaam, tabelNaam, veldNaam, value);

		boolean canDelete = canDeleteData(rows, database, tabel, keyPairs);
		if (canDelete)
		{
			tabel.getData().removeAll(rows);
			System.out.println("Gegevens uit tabel " + tabelNaam + " verwijderd");
			return;
		}
		throw new IntegrityConstraintViolationException("Foreign Keys voldoen niet");
	}

	private boolean canDeleteData(List<Row> rows, Database database, Tabel tabel, List<KeyPair> keyPairs)
	{
		boolean foreignKeysValid = false;
		for (Row row : rows)
		{
			for (KeyPair keyPair : keyPairs)
			{
				Map<String, String> foreignKeyPropertyMap = util.buildForeignToKeyPropertyMap(database, tabel, keyPair,
					row.getData());
				foreignKeysValid = util.isForeignKeyConstraintNotValid(foreignKeyPropertyMap, keyPair.getFromKey()) ? foreignKeysValid
					: true;
			}
		}
		return !foreignKeysValid;
	}

}
